/*
 * NodeImpl.cpp
 *
 *  Created on: Apr 19, 2009
 *      Author: wrice
 */

#include "DOMCoreImp.h"
namespace DOMCoreImp {

NodeImp::NodeImp(DOMString nodeName, DocumentImp * ownerDocument)
: nodeName_(nodeName), nodeValue_(""), childNodes_(new NodeListImp())
, parentNode_(NULL), ownerDocument_(ownerDocument)
{}

DOMString NodeImp::getNodeName() { return nodeName_; }

DOMString NodeImp::getNodeValue() throw(DOMExceptionPtr) {
	return nodeValue_;
}

void NodeImp::setNodeValue(DOMString value) throw(DOMExceptionPtr) {
	nodeValue_ = value;
}

Node * NodeImp::getParentNode() { return parentNode_; }

NodeList * NodeImp::getChildNodes() {
	return NodeListPtr::GetPointer( childNodes_ );
}

Node * NodeImp::getFirstChild() {
	if( false == hasChildNodes() ) return NULL;
	return getChildNodes()->item(0);
}

Node * NodeImp::getLastChild() {
	if( false == hasChildNodes() ) return NULL;
	return getChildNodes()->item(getChildNodes()->getLength() -1);
}

Node * NodeImp::getPreviousSibling() {
	if( false == hasParentNode() ) return NULL;
	const unsigned long idxOnParent = getMyIndexOnParent(this);
	if(0 == idxOnParent) return NULL;
	return getParentNode()->getChildNodes()->item(idxOnParent -1);
}

Node * NodeImp::getNextSibling() {
	if( false == hasParentNode() ) return NULL;
	const unsigned long idxOnParent = getMyIndexOnParent(this);
	if(getParentNode()->getChildNodes()->getLength() -1 <= idxOnParent) return NULL;
	return getParentNode()->getChildNodes()->item(idxOnParent +1);
}

Document * NodeImp::getOwnerDocument() { return ownerDocument_; }

bool NodeImp::hasChildNodes() { return getChildNodes()->getLength() != 0; }

bool NodeImp::hasAttributes() { return false; }

NamedAttrMap * NodeImp::getAttributes() { return NULL; }

bool NodeImp::hasChildNode( Node * refChild ) {
	for( unsigned long idx = 0; idx < getChildNodes()->getLength(); ++idx )
		if( getChildNodes()->item( idx ) == refChild )
			return true;
	return false;
}

Node * NodeImp::insertBefore( Node * newChild, Node * refChild ) throw(DOMExceptionPtr) {
	if(NULL == newChild) return NULL;
	if(NULL == refChild) return NULL;

	if( false == isNodeInTheSameDocument( newChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::WRONG_DOCUMENT_ERR ) );

	if( false == hasChildNode( refChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::NOT_FOUND_ERR ) );

	const unsigned long pos = getMyIndexOnParent( refChild );
	Node * const rst = childNodes_->insert( pos, newChild );
	setMyselfAsParentNodeOf( newChild );
	return rst;
}
Node * NodeImp::replaceChild( Node * newChild, Node * oldChild ) throw (DOMExceptionPtr) {
	if(NULL == newChild) return NULL;
	if(NULL == oldChild) return NULL;

	if( false == isNodeInTheSameDocument( newChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::WRONG_DOCUMENT_ERR ) );

	if( false == hasChildNode( oldChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::NOT_FOUND_ERR ) );

	const unsigned long pos = getMyIndexOnParent(oldChild);
	Node * removed = childNodes_->remove(pos);
	childNodes_->insert(pos, newChild);
	setMyselfAsParentNodeOf( newChild );
	setNullAsParentNodeOf( removed );
	return removed;
}
Node * NodeImp::removeChild( Node * oldChild) throw(DOMExceptionPtr) {
	if( false == hasChildNode( oldChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::NOT_FOUND_ERR ) );

	const unsigned long pos = getMyIndexOnParent(oldChild);
	Node * removed = childNodes_->remove(pos);
	setNullAsParentNodeOf( removed );
	return removed;
}

void NodeImp::setMyselfAsParentNodeOf( Node * newChild ) {
	NodeImp * newChildImp = dynamic_cast< NodeImp * >( newChild );
	newChildImp->parentNode_ = this;
}

bool NodeImp::isNodeInTheSameDocument( Node * aNode ) {
	Document * myDoc = getOwnerDocument();
	if( NULL == myDoc )
		myDoc = dynamic_cast< Document *>( this );
	return aNode->getOwnerDocument() == myDoc;
}

Node * NodeImp::appendChild( Node * newChild) throw(DOMExceptionPtr) {
	if(NULL == newChild) return NULL;

	if( false == isNodeInTheSameDocument( newChild ) )
		throw DOMExceptionPtr(new DOMExceptionImp( DOMException::WRONG_DOCUMENT_ERR ) );

	Node * const rst = childNodes_->insert(getChildNodes()->getLength(), newChild);
	setMyselfAsParentNodeOf( newChild );
	return rst;
}

bool NodeImp::hasParentNode() {
	return NULL != getParentNode();
}

// events
void NodeImp::addEventListener(DOMString type, EventListener * listener, bool useCapture)
{
	Type_EventListener newItem;
	Field< TypeNameE >( newItem ) = type;
	Field< EventListenerE >( newItem ) = listener;
	getContainer( useCapture ).insert( newItem );
}

void NodeImp::removeEventListener(DOMString type, EventListener * listener, bool useCapture)
{
	Type_EventListener newItem;
	Field< TypeNameE >( newItem ) = type;
	Field< EventListenerE >( newItem ) = listener;
	getContainer( useCapture ).erase( newItem );
}

bool NodeImp::dispatchEvent(Event * evt) throw(EventExceptionPtr)
{
	const DOMEvent::DOMString evtType = evt->getType();
	if(evtType.empty())
		throw (EventExceptionPtr) (new EventExceptionImp());

	const bool useCapturingListeners = (evt->getTarget() != this);
	EventListeners listeners = getContainer(useCapturingListeners);

	for( EventListenersIterator type_listener = listeners.begin(); type_listener != listeners.end(); ++type_listener )
		if( Field< TypeNameE >( *type_listener ) == evtType )
			Field< EventListenerE >( *type_listener )->handleEvent(evt);
	return true; // this must be false when one of listener calls the method, "preventDefault", but I have no idea how to figure it out.
}

NodeImp::EventListeners & NodeImp::getContainer(bool useCapture) {
	return ( useCapture ) ? eventListenersForCapturing_ : eventListenersForMyself_;
}

DocumentImp * NodeImp::getOwnerDocumentImp() {
	return ownerDocument_;
}

void NodeImp::setParentNode( NodeImp * parent ) {
	parentNode_ = parent;
}

Event * NodeImp::createEvent(const DOMString eventType) throw(DOMExceptionPtr) {
	const DOMString mouseEventTypes[] = { MouseEventImp::EVENT_CLICK,
									MouseEventImp::EVENT_MOUSE_DOWN,
									MouseEventImp::EVENT_MOUSE_UP,
									MouseEventImp::EVENT_MOUSE_OVER,
									MouseEventImp::EVENT_MOUSE_MOVE,
									MouseEventImp::EVENT_MOUSE_OUT,
									MouseEventImp::EVENT_DBL_CLICK };
	const bool bubbles[]     = { true, true, true, true, true,  true, true };
	const bool cancelables[] = { true, true, true, true, false, true, true };

	for( unsigned int idx = 0; idx < sizeof(bubbles)/sizeof(bool); ++idx ) {
		if( mouseEventTypes[idx] == eventType )
			return new MouseEventImp(
						this, NULL,
						mouseEventTypes[idx], bubbles[idx], cancelables[idx],
						NULL, 0,
						0, 0, 1, 1,
						false, false, false, false,
						MouseEvent::LEFT_MOUSE_BUTTON,
						NULL);
	}

	throw std::runtime_error("Not supported.");
}

unsigned long NodeImp::getMyIndexOnParent(Node * node) throw(DOMExceptionPtr)
{
	Node * parent = node->getParentNode();
	NodeList * siblings = parent->getChildNodes();
	for( unsigned long i = 0; i < siblings->getLength(); ++i )
		if( siblings->item(i) == node )
			return i;
	throw DOMExceptionPtr(new DOMExceptionImp(DOMException::INVALID_ACCESS_ERR));
}

void NodeImp::setNullAsParentNodeOf( Node * node ) {
	NodeImp * nodeImp = dynamic_cast< NodeImp * >( node );
	nodeImp->setParentNode( NULL );
}

}
